#pragma once

#include <vector>
#include <memory>
#include <numeric>
#include <cassert>

namespace tinyUtil {
    template<typename T>
    class TinyObjectPool final {
    private:
        std::vector<T*> chunks_;
        std::vector<T*> freeObject_;
        std::allocator<T> allocator_;
        static constexpr size_t INITIAL_CHUNK_SIZE = 5;
        size_t newChunkSize_ = INITIAL_CHUNK_SIZE;
        void addChunk();
    public:
        TinyObjectPool() = default;
        ~TinyObjectPool();

        // 禁止拷贝构造函数和拷贝赋值函数
        TinyObjectPool(const TinyObjectPool&) = delete;
        TinyObjectPool& operator=(const TinyObjectPool&) = delete;

        // 允许移动构造函数和移动赋值函数
        TinyObjectPool(TinyObjectPool&&) noexcept = default;
        TinyObjectPool& operator=(TinyObjectPool&&) noexcept = default;

        template<typename... Args>
        std::shared_ptr<T> acquireObject(Args&&... args);
    };
}

template<typename T>
void tinyUtil::TinyObjectPool<T>::addChunk() {
    T* newObjectChunk { allocator_.allocate(newChunkSize_) };
    chunks_.push_back(newObjectChunk);

    size_t oldFreeObjectsSize = freeObject_.size();
    freeObject_.resize(oldFreeObjectsSize + newChunkSize_);
    std::iota(std::begin(freeObject_) + oldFreeObjectsSize, std::end(freeObject_), newObjectChunk);
    newChunkSize_ *= 2;
}

template<typename T>
template<typename... Args>
std::shared_ptr<T> tinyUtil::TinyObjectPool<T>::acquireObject(Args&&... args) {
    if (freeObject_.empty()) {
        addChunk();
    }
    T * object = freeObject_.back();
    freeObject_.pop_back();

    new (object) T { std::forward<Args>(args)... };
    return { object, [this](T* obj) {
        obj->~T();
        freeObject_.push_back(obj);
    }};
}

template<typename T>
tinyUtil::TinyObjectPool<T>::~TinyObjectPool() {
    // 请务必确保对象池自身的生命周期超出对象池给出的所有对象的生命周期
    // 下面对该情况断言
    assert(freeObject_.size() == INITIAL_CHUNK_SIZE * (1 << (chunks_.size() - 1)));

    size_t chunkSize = INITIAL_CHUNK_SIZE;
    for (T* chunk : chunks_) {
        allocator_.deallocate(chunk, chunkSize);
        chunkSize *= 2;
    }
    chunks_.clear();
}
